/*
 * Copyright 2008-2009 Aying.Net Powered by Fuchun.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package net.aying.util.security;

import javax.crypto.spec.SecretKeySpec;
import java.security.NoSuchAlgorithmException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import net.aying.util.StringUtils;
import net.aying.util.resources.Resources;

/**
 * {@code Crypto} 是一个抽象类，它为字符串、文本文件等介质提供了一组加密/解密的方法。
 * <p>
 * {@code Crypto} 提供了一个类方法 {@code getInstance}，以获得一个此类型的通用对象。 {@code Crypto} 的
 * {@code getInstance} 方法返回一个 {@code Crypto} 的实例对象。
 * </p>
 * 
 * @author Fuchun
 * @version 1.0, 2009-6-18
 */
public abstract class Crypto {

    private static final Log      log                     = LogFactory.getLog(Crypto.class);

    /** <code>DES</code> 算法的名称。 */
    public static final String    DES_ALGORITHM           = "DES";

    /** <code>MD5</code> 算法的名称。 */
    public static final String    MD5_ALGORITHM           = "MD5";

    /** <code>SHA</code> 算法的名称。 */
    public static final String    SHA_ALGORITHM           = "SHA-1";

    /** 加密文本文件时，备份的文件默认扩展名。 */
    public static final String    BACKUP_FILE_EXTENSION   = "bak";

    /** 表示为极弱密钥。 */
    public static final int       DELICATE_KEY_SPEC       = 0;

    /** 表示为简单弱密钥。 */
    public static final int       SIMPLE_KEY_SPEC         = 1;

    /** 表示为中等密钥。 */
    public static final int       MEDIUM_KEY_SPEC         = 2;

    /** 表示为较强密钥。 */
    public static final int       PIU_FORTE_KEY_SPEC      = 3;

    /** 表示为强密钥。 */
    public static final int       STRONG_KEY_SPEC         = 4;

    /** 表示密钥换算为 {@code byte} 的长度大于等于该值，并且小于 {@link #MEDIUM_KEY_SPEC_LEN} 时，为简单密钥。 */
    protected static final int    SIMPLE_KEY_SPEC_LEN     = 8;

    /** 表示密钥换算为 {@code byte} 的长度大于该值，小于 {@link #PIU_FORTE_KEY_SPEC_LEN} 值时，为中等密钥。 */
    protected static final int    MEDIUM_KEY_SPEC_LEN     = SIMPLE_KEY_SPEC_LEN * 2;

    /** 表示密钥换算为 {@code byte} 的长度大于该值，小于 {@link #STRONG_KEY_SPEC_LEN} 值时，为较强密钥。 */
    protected static final int    PIU_FORTE_KEY_SPEC_LEN  = SIMPLE_KEY_SPEC_LEN * 3;

    /** 表示密钥换算为 {@code byte} 的长度大于该值时，为强密钥。 */
    protected static final int    STRONG_KEY_SPEC_LEN     = SIMPLE_KEY_SPEC_LEN * 4;

    /**
     * 默认的 {@code KeySpec} 的密钥内容(该字符串的前8个字节将作为密钥的内容)的Key。
     */
    public static final String    DEFAULT_KEY_SPEC_STRING = "Crypto.DefaultSecretKey";

    /* 不支持给定加密/解密算法的错误提示信息资源键。 */
    protected static final String NO_SUCH_ALGORITHM       = "Crypto.Error.NoSuchAlgorithm";

    /* 不支持给定加密/解密算法的填充方案错误提示信息资源键。 */
    protected static final String NO_SUCH_PADDING         = "Crypto.Error.NoSuchPadding";

    /* 解密时解码密文出错的提示信息资源键。 */
    protected static final String DECODER_ERROR           = "Crypto.Error.Decoder";

    /* 如果给定的 key 不适合初始化此 Cipher 的错误提示信息资源键。 */
    protected static final String INVALID_KEY             = "Crypto.Error.InvalidKey";

    /* 加密/解密时 Cipher 尚未初始化的错误提示信息资源键。 */
    protected static final String ILLEGAL_STATE           = "Crypto.Error.IllegalState";

    /* 加密算法无法处理所提供的输入数据的错误提示信息资源键。 */
    protected static final String ILLEGAL_BLOCK_SIZE      = "Crypto.Error.IllegalBlockSize";

    /* Cipher 解密时未请求填充（或不填充），但解密的数据没有用适当的填充字节进行限制的错误提示信息资源键。 */
    protected static final String BAD_PADDING             = "Crypto.Error.BadPadding";

    /* 要实现的算法名称 */
    private String                algorithm;

    /** 加密(解密)的密钥。 */
    protected SecretKeySpec       secretKeySpec;

    /**
     * 默认的构造方法，构造一个新的 {@code Crypto}。
     */
    protected Crypto() {
        super();
    }

    /**
     * 返回基于 {@code DES} 算法的 {@code Crypto} 对象。若当前平台不支持 {@code DES} 算法，则返回
     * {@code null}。
     * 
     * @return 基于 {@code DES} 算法的 {@code Crypto}。
     */
    public static Crypto getInstance() {
        return getInstance(DEFAULT_KEY_SPEC_STRING);
    }

    /**
     * 返回指定密钥的基于 {@code DES} 算法的 {@code Crypto} 对象。若当前平台不支持 {@code DES} 算法，则返回
     * {@code null}。
     * 
     * @param sKeySpec 指定的密钥字符串。
     * @return 基于 {@code DES} 算法的 {@code Crypto}。
     */
    public static Crypto getInstance(String sKeySpec) {
        return getInstance(sKeySpec, DES_ALGORITHM);
    }

    /**
     * 返回基于指定算法并指定密钥的 {@code Crypto} 对象。若当前平台不支持指定的 {@code algorithm} 算法，则返回
     * {@code null}。若指定的密钥 {@code sKeySpec} 为 {@code null} 或空字符串，则默认为
     * {@link #DEFAULT_KEY_SPEC_STRING} 的值。若指定的算法 {@code algorithm} 为
     * {@code null} 或空字符串， 则默认为 {@link #DES_ALGORITHM}。
     * 
     * @param sKeySpec 指定的密钥字符串。
     * @param algorithm 指定的加密/解密算法。
     * @return 基于指定算法和指定密钥的 {@code Crypto} 对象。
     */
    public static Crypto getInstance(String sKeySpec, String algorithm) {
        if (StringUtils.isNullOrEmpty(algorithm)) algorithm = DES_ALGORITHM;
        try {
            return createCrypto(sKeySpec, algorithm);
        } catch (NoSuchAlgorithmException ex) {
            log.error(ex.getMessage());
            return null;
        }
    }

    /* 创建一个 Crypto。 */
    private static Crypto createCrypto(String sKeySpec, String algorithm)
            throws NoSuchAlgorithmException {
        if (StringUtils.isNullOrEmpty(sKeySpec))
            sKeySpec = Resources.getText(DEFAULT_KEY_SPEC_STRING);
        if (!StringUtils.inArray(algorithm, DES_ALGORITHM, MD5_ALGORITHM, SHA_ALGORITHM)) {
            throw new NoSuchAlgorithmException(Resources.getMessage(NO_SUCH_ALGORITHM, algorithm));
        }
        if (DES_ALGORITHM.equalsIgnoreCase(algorithm)) {
            return new DESCrypto(sKeySpec);
        }
        return null;
    }

    /**
     * 返回当前 {@code Crypto} 的密钥强度。
     * <ul>
     * <li>极弱：{@link #DELICATE_KEY_SPEC}；</li>
     * <li>弱：{@link #SIMPLE_KEY_SPEC}；</li>
     * <li>中等：{@link #MEDIUM_KEY_SPEC}；</li>
     * <li>较强：{@link #PIU_FORTE_KEY_SPEC}；</li>
     * <li>强：{@link #STRONG_KEY_SPEC}；</li>
     * </ul>
     * 
     * @return 密钥强度。
     */
    public int getSecretKeyIntensity() {
        byte[] keySpecBytes = getSecretKeySpec().getEncoded();
        int keySpecLength = keySpecBytes.length;
        if (keySpecLength < SIMPLE_KEY_SPEC_LEN)
            return DELICATE_KEY_SPEC;
        else if (keySpecLength >= SIMPLE_KEY_SPEC_LEN && keySpecLength < MEDIUM_KEY_SPEC_LEN)
            return SIMPLE_KEY_SPEC;
        else if (keySpecLength >= MEDIUM_KEY_SPEC_LEN && keySpecLength < PIU_FORTE_KEY_SPEC_LEN)
            return MEDIUM_KEY_SPEC;
        else if (keySpecLength >= PIU_FORTE_KEY_SPEC_LEN && keySpecLength < STRONG_KEY_SPEC_LEN)
            return PIU_FORTE_KEY_SPEC;
        else
            return STRONG_KEY_SPEC;
    }

    /**
     * 对给定的字符串进行加密操作，返回使用 {@link #getAlgorithm()} 加密(解密)算法加密的新的字符串。
     * <p>
     * 若当前环境不支持特定的加密/解密算法，则返回 {@code null}。
     * </p>
     * 
     * @param plaintext 要加密的字符串。
     * @return 给定字符串的加密密文。
     */
    public String encrypt(String plaintext) {
        if (plaintext == null) throw new RuntimeException("The plaintext is null.");
        return encryptString(plaintext);
    }

    /**
     * 对给定的字符串进行解密处理，返回 {@link #getAlgorithm()} 加密(解密)算法解密的字符串。
     * <p>
     * 若当前的加密(解密)算法及密钥和给定的经加密的 {@code encrypttext} 字符串的算法及密钥不一致，则 无法进行解密，返回
     * {@code null}。
     * </p>
     * 
     * @param encrypttext 要解密的密文字符串。
     * @return 给定的密文字符串的原文字符串。若解密失败，则返回 {@code null}。
     */
    public String decrypt(String encrypttext) {
        if (StringUtils.isNullOrEmpty(encrypttext))
            throw new RuntimeException("The encrypttext is null or empty.");
        return decryptString(encrypttext);
    }

    /**
     * 对给定的字符串进行加密操作，返回使用 {@link #getAlgorithm()} 加密(解密)算法加密的新的字符串。
     * 
     * @param plaintext 要加密的字符串。
     * @return 给定字符串的加密密文。
     */
    protected abstract String encryptString(String plaintext);

    /**
     * 对给定的字符串进行解密处理，返回 {@link #getAlgorithm()} 加密(解密)算法解密的字符串。
     * 
     * @param encrypttext 要解密的密文字符串。
     * @return 给定的密文字符串的原文字符串。若解密失败，则返回 {@code null}。
     */
    protected abstract String decryptString(String encrypttext);

    /**
     * 获取算法名称。
     * 
     * @return 算法名称。
     */
    public String getAlgorithm() {
        return algorithm;
    }

    /**
     * 设置算法名称。
     * 
     * @param algorithm 要设置的 算法名称。
     */
    protected void setAlgorithm(String algorithm) {
        this.algorithm = algorithm;
    }

    /**
     * 获取加密(解密)的密钥。
     * 
     * @return 加密(解密)的密钥。
     */
    public SecretKeySpec getSecretKeySpec() {
        return secretKeySpec;
    }

    /**
     * 设置加密(解密)的密钥。
     * 
     * @param secretKeySpec 要设置的 加密(解密)的密钥。
     */
    public void setSecretKeySpec(SecretKeySpec secretKeySpec) {
        this.secretKeySpec = secretKeySpec;
    }
}
